﻿// <copyright file="BlogEntryService.cs" company="Clever Software">
// Copyright (c) 2013, 2014 All Right Reserved, http://www.cleversoftware.by
//
// This source is subject to the Microsoft Permissive License.
// Please see the License.txt file for more information.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// </copyright>
// <author>Clever Software</author>
// <email>info@cleversoftware.by</email>
// <date>2014-02-04</date> 
// <summary>CleverCMS source code</summary>

namespace CleverCMS.Blog.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using EntityFramework.Extensions;
    using WebMatrix.WebData;

    using Blog.Data;
    using Blog.Data.Entities;
    using Blog.Data.Extensions;
    using Blog.Services.ViewModels;
    
    using Core.Infrastructure;
    using Core.Services;
    using CleverCMS.Core.Exceptions;

    public class BlogEntryService : BlogGenericService<BlogEntry>
    {
        private readonly IBlogGenericRepository<BlogEntry>          m_blogRepo;
        private readonly IBlogGenericRepository<BlogEntryCategory>  m_blogCategoriesRepo;
        private readonly IBlogGenericRepository<BlogEntryComment>   m_blogCommentsRepo;
        private readonly LoggingService                             m_log;
        private readonly IBlogGenericRepository<BlogEntryTag>       m_tagRepo;
        private readonly IBlogGenericRepository<BlogEntryRating>    m_ratingRepository;
        private readonly AppCache                                   m_appCache;

        /// <summary>
        /// .ctor
        /// </summary>
        /// <param name="pageRepo"></param>
        /// <param name="blogRepo"></param>
        /// <param name="blogCommentsRepo"></param>
        /// <param name="blogCatRepo"></param>
        /// <param name="log"></param>
        /// <param name="tagRepo"></param>
        /// <param name="ratingRepository"></param>
        /// <param name="accountService"></param>
        public BlogEntryService(IBlogGenericRepository<BlogEntry>           blogRepo,
                                IBlogGenericRepository<BlogEntryComment>    blogCommentsRepo,
                                IBlogGenericRepository<BlogEntryCategory>   blogCatRepo,
                                LoggingService                              log,
                                IBlogGenericRepository<BlogEntryTag>        tagRepo,
                                IBlogGenericRepository<BlogEntryRating>     ratingRepository) : base(blogRepo)
        {
            this.m_blogRepo = blogRepo;
            this.m_blogCommentsRepo = blogCommentsRepo;
            this.m_blogCategoriesRepo = blogCatRepo;
            this.m_log = log;
            this.m_tagRepo = tagRepo;
            this.m_ratingRepository = ratingRepository;
            this.m_appCache = new AppCache();
        }

        #region Blog Category
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<BlogEntryCategory> LoadCategoriesPaged(int start, int size, string sortOrder, out int count)
        {
            var cats = m_blogCategoriesRepo.LoadAll().OrderByDescending(x => x.CategoryName)
                                                     .Skip(start)
                                                     .Take(size);
            count = cats.Count();
            return cats;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<BlogEntryCategory> LoadAllCategories()
        {
            return m_blogCategoriesRepo.LoadAll().OrderByDescending(x => x.CategoryName);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        /// <returns></returns>
        public BlogEntryCategory UpdateCategory(BlogEntryCategory entry)
        {
             m_blogCategoriesRepo.Save(entry, entry.Id == 0);
             m_appCache.Remove(new string[] { "BlogEntries", "AdminBlogEntries", "Categories" });
             return entry;
        }
        /// <summary>
        /// Delete blog category by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool DeleteCategory(int id)
        {
            var deleted = false;
            var defCat = m_blogCategoriesRepo.LoadAll(x => x.CategoryName.ToLower().Contains("default")).SingleOrDefault();
            if (defCat != null && id != defCat.Id)
            {
                m_blogRepo.LoadAll(null, null, "").Update(t => t.CategoryId == id, t2 => new BlogEntry { CategoryId = defCat.Id });
                m_blogCategoriesRepo.Delete(id);
                deleted = true;
            }
            m_appCache.Remove(new string[] { "BlogEntries", "AdminBlogEntries", "Categories"});
            return deleted;
        }

        #endregion

        #region Blog Posts
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<BlogEntry> LoadPostsPaged(int? id, int start, int size, string sortOrder, out int count)
        {
            var postsQry = m_blogRepo.LoadAll().Where(y => y.CategoryId == id);
            
            count = postsQry.Count();

            return postsQry.OrderByDescending(x => x.Created)
                           .Skip(start)
                           .Take(size); 
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<BlogEntry> LoadAll(int start, int size, string sortOrder, out int count)
        {
            var pages = m_blogRepo.LoadAll(orderBy: q => q.OrderByDescending(d => d.Id))
                                  .Skip(start)
                                  .Take(size);

            count = m_blogRepo.Count();

            return pages;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="includeInvisibleEntries"></param>
        /// <param name="tag"></param>
        /// <param name="search"></param>
        /// <param name="categoryId"></param>
        /// <returns></returns>
        public IQueryable<BlogEntry> GetAll(bool includeInvisibleEntries, string tag, string search, string categoryId)
        {
            var query = m_blogRepo.LoadAll();

            if (!includeInvisibleEntries)
                query = query.Where(e => e.Visible);

            if (!string.IsNullOrEmpty(categoryId))
                query = query.Where(x => x.Category.Alias == categoryId);

            if (!string.IsNullOrEmpty(search))
                foreach (var item in search.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
                    query = query.Where(e => e.Header.Contains(item) || e.ShortContent.Contains(item) || e.Content.Contains(item));

            if (!string.IsNullOrEmpty(tag))
                query = query.Where(x => x.BlogEntryTags.Any(y=>y.Name == tag));

            query = query.OrderByDescending(s => s.Created);
            return query;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="month"></param>
        /// <param name="year"></param>
        /// <param name="categoryId"></param>
        /// <returns></returns>
        public IQueryable<BlogEntry> GetForArchive(int month,int year, string categoryId)
        {
            var query = this.m_blogRepo.LoadAll().Where(x => (x.Created.Month == month) && (x.Created.Year == year));

            if (!string.IsNullOrEmpty(categoryId))
                query = query.Where(x => x.Category.Alias == categoryId);

            query = query.OrderByDescending(s => s.Created);
            return query;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="includeInvisibleEntries"></param>
        /// <param name="header"></param>
        /// <returns></returns>
        public BlogEntry GetByUrl(bool includeInvisibleEntries, string header)
        {
            BlogEntry entry = null;
            try
            {
                entry = this.m_blogRepo.LoadAll().FirstOrDefault(e => (e.Visible || includeInvisibleEntries) && e.Alias.Equals(header));
            }
             catch (Exception ex)
             {
                 m_log.Warn(string.Format("Page not found: name={0}", header));
                 m_log.Exception(ex);
                 throw new PageNotFoundException("The requested post was not found.");
             }
            return entry;
        }
        /// <summary>
        /// Returs blog category by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public BlogEntryCategory GetCategoryById(int id)
        {
            BlogEntryCategory category = null;
            try
            {
                category = this.m_blogCategoriesRepo.Load(id);
            }
            catch (Exception ex)
            {
                m_log.Warn(string.Format("Category not found: id={0}", id));
                m_log.Exception(ex);
                throw new PageNotFoundException("The requested category was not found.");
            }
            return category;
        }
        /// <summary>
        /// Returns all blog categories
        /// </summary>
        /// <returns></returns>
        public IQueryable<BlogEntryCategory> GetAllCategories()
        {
            return m_blogCategoriesRepo.LoadAll();
        }
        /// <summary>
        /// Returns 10 tags for most popular blog entries
        /// </summary>
        /// <returns></returns>
        public IQueryable<BlogEntryTag> GetAllTags()
        {
            return m_tagRepo.LoadAll(x => x.BlogEntries.Any())
                            .OrderByDescending(x => x.BlogEntries.Count)
                            .Select(tag => tag)
                            .Take(10);
        }

        //TODO: Group by years!
        /// <summary>
        /// Returns years of blog entries
        /// </summary>
        /// <returns></returns>
        public IEnumerable<DateTime> GetAllDates()
        {
            var dates = m_blogRepo.LoadAll()
                                .Select(x => x.Created)
                                .Select(y => new { y.Month })
                                .Distinct()
                                .ToList()
                                .Select(z => new DateTime(2014, z.Month, 1));
            return dates;
        }
        /// <summary>
        /// Returns latest blog
        /// </summary>
        /// <returns></returns>
        public IQueryable<BlogEntry> GetLatesBlog()
        {
            var blogs = GetAll().Where(x => x.Visible)
                                .GroupBy(x => x.Category.Alias)
                                .SelectMany(y=>y.OrderByDescending(z => z.Created)
                                .Take(1));

            return blogs.OrderByDescending(x=>x.Created);
        }
        /// <summary>
        /// Returns blog entry by date
        /// </summary>
        /// <param name="includeInvisibleEntries"></param>
        /// <param name="date"></param>
        /// <param name="marker"></param>
        /// <returns></returns>
        public BlogEntry GetByDate(bool includeInvisibleEntries, DateTime date, string marker)
        {
            BlogEntry entry = null;
            try
            {
                entry = this.m_blogRepo.LoadAll().FirstOrDefault(e => (e.Visible || includeInvisibleEntries) &&
                                                                 e.HeaderUrl.Equals(marker) &&
                                                                 e.Created == date);
            }
            catch (Exception ex)
            {
                m_log.Warn(string.Format("Page not found: name={0}", marker));
                m_log.Exception(ex);
                throw new PageNotFoundException("The requested post was not found.");
            }
            return entry;
        }
        /// <summary>
        /// Returns blog entry by id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public BlogEntry GetById(int id)
        {
            BlogEntry entry = null;
            try
            {
                entry = this.m_blogRepo.Load(id);
            }
            catch (Exception ex)
            {
                m_log.Warn(string.Format("Page not found: id={0}", id));
                m_log.Exception(ex);
                throw new PageNotFoundException("The requested post was not found.");
            }
            return entry;
        }
        /// <summary>
        /// Saves blog entry
        /// </summary>
        /// <param name="entry"></param>
        public void Save(BlogEntry entry)
        {
            entry.Modified = DateTime.Now;
            entry.Published = DateTime.Now;
            
            FillTags(entry);
            EnsureFieldsAreFilled(entry);

            //TODO: Review tags logic
            //post.Tags.Where(x => x.Id != 0).ToList().ForEach(x => context.Entry<CmsTag>(x).State = EntityState.Unchanged);

            m_blogRepo.Save(entry, entry.Id == 0);
            m_appCache.Remove(new string[] { "BlogEntries", "AdminBlogEntries" });
            SearchService.AddUpdateLuceneIndex(entry);
        }
        
        /// <summary>
        /// Add view count on 1
        /// </summary>
        /// <param name="entry"></param>
        public void AddViewCount(BlogEntry entry)
        {
            entry.ViewCount += 1;
            m_blogRepo.Save(entry, entry.Id == 0);
        }

        /// <summary>
        /// Creates new blog entry
        /// </summary>
        /// <param name="entry"></param>
        public void CreateEntry(BlogEntry entry)
        {
            entry.Modified = DateTime.Now;
            entry.Created = DateTime.Now;
            entry.Visible = true;
          
            EnsureFieldsAreFilled(entry);

            //TODO: Review tags logic
            //post.Tags.Where(x => x.Id != 0).ToList().ForEach(x => context.Entry<CmsTag>(x).State = EntityState.Unchanged);

            m_blogRepo.Save(entry, true);
            SearchService.AddUpdateLuceneIndex(entry);
            m_appCache.Remove(new string[] { "BlogEntries", "AdminBlogEntries" });
        }

        //TODO: Review Tags logic
        #region To refactor with PageSevice

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entry"></param>
        private void EnsureFieldsAreFilled(BlogEntry entry)
        {
            if (string.IsNullOrEmpty(entry.SeoTitle))
                entry.SeoTitle = entry.Header;

            if (string.IsNullOrEmpty(entry.MetaKeywords))
                entry.MetaKeywords = entry.BlogEntryTags.ToTagString();

            if (string.IsNullOrEmpty(entry.MetaDescription))
                entry.MetaDescription = entry.Header;

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="post"></param>
        private void FillTags(BlogEntry post)
        {
            if (null == post)
                return;

            if (null != post.TagString && !string.IsNullOrEmpty(post.TagString.AttemptedValue))
            {
                List<String> tags = post.TagString.AttemptedValue.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                                                                 .Where(x => !String.IsNullOrEmpty(x))
                                                                 .Select(x => x.Trim())
                                                                 .ToList();

                foreach (string tag in tags)
                {
                    if (post.BlogEntryTags.Where(x => x.Name == tag).SingleOrDefault() == null)
                    {
                        var cmsTag = m_tagRepo.LoadAll(x => x.Name == tag).SingleOrDefault();
                        if (null != cmsTag)
                            post.BlogEntryTags.Add(cmsTag);
                        else
                            post.BlogEntryTags.Add(new BlogEntryTag { Name = tag.Trim() });
                    }
                }

                var toDelete = post.BlogEntryTags.Where(x => !tags.Contains(x.Name)).ToList();
                toDelete.ForEach(x => post.BlogEntryTags.Remove(x));

                m_appCache.Remove("BlogTags");
            }
        }

        #endregion

        public void DeletePost(int id)
        {
            var post = m_blogRepo.LoadAll().FirstOrDefault(x => x.Id == id); 
            if (post != null)
            {
                string alias = post.Alias;
                m_blogRepo.Delete(id);

                m_appCache.Remove(new string [] {"BlogEntries","AdminBlogEntries"});
                SearchService.ClearLuceneIndexRecord(alias);
            }
        }

        #endregion

        #region Blog Comments
        /// <summary>
        /// Adds comment
        /// </summary>
        /// <param name="comment"></param>
        public void AddComment(BlogEntryComment comment) 
        {
            m_blogCommentsRepo.Save(comment, comment.Id == 0);
        }
        /// <summary>
        /// Returns comments for a BlogEntry
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public ICollection<BlogEntryComment> GetCommentsById(int id)
        {
            return m_blogCommentsRepo.LoadAll().Where(x => x.BlogEntryId == id).ToList();
        }
        /// <summary>
        /// Returns comments for a specific branch
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parentId"></param>
        /// <param name="comment"></param>
        /// <returns></returns>
        public ICollection<BlogEntryComment> GetComment(string name, int? parentId = null, string comment = null)
        {
            var query = m_blogCommentsRepo.LoadAll().Where(x => x.Name == name);

            if (parentId != null)
                query = query.Where(x => x.ParentId == parentId);

            if (comment != null)
                query = query.Where(x => x.Comment == comment);

            return query.ToList();
        }
        /// <summary>
        /// Returns all comments for a paged list
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<BlogEntryComment> GetAllComments(int start, int size, string sortOrder, out int count)
        {
            var query = m_blogCommentsRepo.LoadAll();
            
            //TODO: Can we use CASE here?
            if (string.IsNullOrEmpty(sortOrder) || sortOrder.Equals("Id ASC"))
            {
                query = query.OrderBy(p => p.Id);
            }
            else if (sortOrder.Equals("Id DESC"))
            {
                query = query.OrderByDescending(p => p.Id);
            }
            else if (sortOrder.Equals("Name ASC"))
            {
                query = query.OrderBy(p => p.Name);
            }
            else if (sortOrder.Equals("Name DESC"))
            {
                query = query.OrderByDescending(p => p.Name);
            }
            else if (sortOrder.Equals("Created ASC"))
            {
                query = query.OrderBy(p => p.CreatedAt);
            }
            else if (sortOrder.Equals("Created DESC"))
            {
                query = query.OrderByDescending(p => p.CreatedAt);
            }
            else
            {
                query = query.OrderBy(p => p.Id);
            }

            count = m_blogCommentsRepo.Count();

            return count > 0 ? query.Skip(start).Take(size) : query;
        }
        /// <summary>
        /// Returns an Blog entry with comments
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public string GetCommentedObject(int id)
        {
            var commentObj = m_blogCommentsRepo.Load(id);
            
            //TODO: Do we need to load Blogentry here at all
            return m_blogRepo.Load(commentObj.BlogEntryId).HeaderUrl;
        }
        /// <summary>
        /// Deletes comment
        /// </summary>
        /// <param name="id"></param>
        public void DeleteComment(int id)
        {
            m_blogCommentsRepo.Delete(id);
        }

        #endregion

        #region Blog Ratings
        /// <summary>
        /// Get rating by post id
        /// </summary>
        /// <param name="postId"></param>
        /// <returns></returns>
        public IQueryable<BlogEntryRating> GetRating(int postId)
        {
            return m_ratingRepository.LoadAll().Where(x => (x.BlogEntryId == postId));
        }
        /// <summary>
        /// Increment rating
        /// </summary>
        /// <param name="rate"></param>
        /// <param name="id"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        public BlogRatingViewModel IncrementPostRating(double rate, int id, string userName)
        { 
           
            if (m_ratingRepository.LoadAll().Any(x => (x.BlogEntryId == id && x.UserId == WebSecurity.CurrentUserId)))
            {
                var listOfRating = GetRating(id).ToList();
                var avg = listOfRating.Sum(x => x.Rating) / listOfRating.Count();

                return new BlogRatingViewModel { TotalRaters = listOfRating.Count(), AverageRating = avg };
            }
            else
            {
                m_ratingRepository.Save(new BlogEntryRating { BlogEntryId = id, UserId = WebSecurity.CurrentUserId, Rating = rate }, true);
                
                var listOfRating = GetRating(id).ToList();
                var avg = listOfRating.Sum(x => x.Rating) / listOfRating.Count();

                return new BlogRatingViewModel { TotalRaters = listOfRating.Count(), AverageRating = avg };
            }
        }
        /// <summary>
        /// Check if post has been rated by the user
        /// </summary>
        /// <param name="id"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool IsRatedBefore(int id, string name)
        {
            var res = m_ratingRepository.LoadAll(x => (x.UserId == WebSecurity.CurrentUserId && x.BlogEntryId == id));
            return res != null && res.ToList().Any();
        }
        #endregion

        #region Dashboard
        /// <summary>
        /// Load dashbard
        /// </summary>
        /// <returns></returns>
        public int GetTotalViewCount()
        {
            var pages = m_blogRepo.LoadAll();
            return pages.Any() ? pages.Sum(x => x.ViewCount) : 0;
        }

        #endregion

        #region Tags
        /// <summary>
        /// 
        /// </summary>
        /// <param name="start"></param>
        /// <param name="size"></param>
        /// <param name="sortOrder"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public IQueryable<BlogEntryTag> GetAllTags(int start, int size, string sortOrder, out int count)
        {
            var pages = m_tagRepo.LoadAll();

            if (string.IsNullOrEmpty(sortOrder) || sortOrder.Equals("Id ASC"))
            {
                pages = pages.OrderBy(p => p.Id);
            }
            else if (sortOrder.Equals("Id DESC"))
            {
                pages = pages.OrderByDescending(p => p.Id);
            }
            else if (sortOrder.Equals("Name ASC"))
            {
                pages = pages.OrderBy(p => p.Name);
            }
            else if (sortOrder.Equals("Name DESC"))
            {
                pages = pages.OrderByDescending(p => p.Name);
            }
            else
            {
                pages = pages.OrderBy(p => p.Id);
            }

            count = m_tagRepo.Count();

            return count > 0 ? pages.Skip(start).Take(size) : pages;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void DeleteTag(int id)
        {
            m_tagRepo.Delete(id);
        }
        #endregion
    }
}